#include "External.h"
#include "Broady.h"
#include "Timer.h"
#include "Application.h"
#include "Socket.h"
#include "Listener.h"
#include "C.h"
#include <windows.h>

#define E_PACKET_PING			1
#define E_PACKET_DATA			2
#define E_PACKET_BROADCAST		3
#define E_PACKET_QUIT			4
#define E_PACKET_SHAKE			5

#include <map>
#include <boost/shared_ptr.hpp>

namespace Broady {

External::External( void )
{
	m_init = false;
}

External::~External( void )
{
	Quit( );
}

bool External::IsInit( void ) const
{
	return m_init;
}

bool External::Init( unsigned int addr )
{
	if( m_init )
	{
		return false;
	}

	m_clients.clear( );
	memset( &m_ip, 0, sizeof( m_ip ) );
	m_port = 0;

	memcpy( &m_ip, &addr, sizeof( m_ip ) );

	OutputMessage( "Addr: %d.%d.%d.%d\n",
			m_ip.byte1,
			m_ip.byte2,
			m_ip.byte3,
			m_ip.byte4 );

	m_sck = Socket::Create( );

	if( !m_sck )
	{
		OutputMessage( "External: Failed to create socket.\n" );

		return false;
	}

	if( !m_sck->Bind( addr, 16788 ) )
	{
		OutputMessage( "Failed to bind socket.\n" );
		m_sck = SocketPtr( );

		return false;
	}

	netNode_t* node = C_network;

	while( node )
	{
		SendShake( 0 /* TODO */, node->ip, node->port );
		node = node->next;
	}

	return ( m_init = true );
}

void External::Quit( void )
{
	for( ClientPtrList::iterator i = m_clients.begin( ), e = m_clients.end( ); i != e; )
	{
		SendGoodbye( i->second );
		i = m_clients.erase( i );
	}

	m_init = false;

	if( m_sck )
	{
		m_sck = SocketPtr( );
	}

	memset( &m_ip, 0, sizeof( m_ip ) );
	m_port = 0;
}

boost::shared_ptr<client_t> External::GetClient( unsigned long ip, unsigned short port )
{
	ClientPtrList::iterator i = m_clients.find( IPPortPair( ip, port ) );

	if( i == m_clients.end( ) )
	{
		return boost::shared_ptr<client_t>( );
	}

	return i->second;
}

boost::shared_ptr<client_t> External::AddClient( unsigned long ip, unsigned short port )
{
	SocketPtr s = Socket::Create( );

	if( !s || !s->SetBroadcast( true ) || !s->Bind( 0, 0 ) )
	{
		return boost::shared_ptr<client_t>( );
	}

	unsigned long node_ip = 0;
	unsigned short node_port = 0;

	if( !s->GetLocalAddress( &node_ip, &node_port ) )
	{
		OutputMessage( "External: Unable to get local socket address.\n" );
	}

	boost::shared_ptr<client_t> client = boost::shared_ptr<client_t>( new client_t );

	client->node = s;
	client->ip = ip;
	client->port = port;
	client->node_ip = node_ip;
	client->node_port = node_port;
	client->pingTimeout.Init( 10000 );
	client->pingInterval.Init( 2000 );

	m_clients[IPPortPair( ip, port )] = client;

	return client;
}

bool External::DelClient( unsigned long ip, unsigned short port )
{
	ClientPtrList::iterator i = m_clients.find( IPPortPair( ip, port ) );

	if( i == m_clients.end( ) )
	{
		return false;
	}

	m_clients.erase( i );

	return true;
}

bool External::SendPing( boost::shared_ptr<client_t> client, unsigned int id )
{
	char buffer[ 16 ];
	unsigned int len = 0;

	*buffer = E_PACKET_PING;
	len++;

	memcpy( buffer + len, &id, sizeof( id ) );
	len += sizeof( id );

	return m_sck->SendTo( buffer, len, client->ip, client->port );
}

bool External::SendPacket( boost::shared_ptr<client_t> client, const unsigned char* packet, unsigned int plen, unsigned short port )
{
	char buffer[ 2048 ];
	unsigned int len = 0;
	ip_address ipaddr = *( ip_address* ) &client->ip;

	*buffer = E_PACKET_DATA;
	len++;

	memcpy( buffer + len, &port, sizeof( port ) );
	len += sizeof( port );

	memcpy( buffer + len, packet, plen );
	len += plen;

	OutputMessage( "Sending Packet for port %u to %u.%u.%u.%u:%u\n", port, ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->port );

	return m_sck->SendTo( buffer, len, client->ip, client->port );
}

bool External::SendBroadcast( boost::shared_ptr<client_t> client, const unsigned char* packet, unsigned int plen, unsigned short port )
{
	char buffer[ 2048 ];
	unsigned int len = 0;
	ip_address ipaddr = *( ip_address* ) &client->ip;

	*buffer = E_PACKET_BROADCAST;
	len++;

	memcpy( buffer + len, &port, sizeof( port ) );
	len += sizeof( port );

	memcpy( buffer + len, packet, plen );
	len += plen;

	OutputMessage( "Sending Broadcast for port %u to %u.%u.%u.%u:%u\n", port, ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->port );

	return m_sck->SendTo( buffer, len, client->ip, client->port );
}

bool External::SendGoodbye( boost::shared_ptr<client_t> client )
{
	char buffer[ 16 ];
	unsigned int len = 0;
	ip_address ipaddr = *( ip_address* ) &client->ip;

	*buffer = E_PACKET_QUIT;
	len++;

	OutputMessage( "Sending Goodbye to %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->port );

	return m_sck->SendTo( buffer, len, client->ip, client->port );
}

bool External::SendShake( unsigned int id, unsigned long ip, unsigned short port )
{
	char buffer[ 16 ];
	unsigned int len = 0;
	ip_address ipaddr = *( ip_address* ) &ip;

	*buffer = E_PACKET_SHAKE;
	len++;

	memcpy( buffer + len, &id, sizeof( id ) );
	len += sizeof( id );

	OutputMessage( "Sending Shake to %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

	return m_sck->SendTo( buffer, len, ip, port );
}

bool External::OnShake( unsigned char* packet, unsigned int len, unsigned long ip, unsigned short port )
{
	unsigned int id = 0;
	ip_address ipaddr = *( ip_address* ) &ip;

	/* TODO: Verify the length of the packet not to get a buffer overflow */
	boost::shared_ptr<client_t> client = GetClient( ip, port );
	memcpy( &id, packet, 4 );
	packet += 4;
	len -= 4;

	if( !client )
	{
		/* TODO: Bind the socket to the client-specific listener IP */
		client = AddClient( ip, port );

		if( !client ) {
			OutputMessage( "Failed to add client..........\n" );
		} else {
			OutputMessage( "%u.%u.%u.%u:%u is now a client ", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );
			ipaddr = *( ip_address* ) &client->node_ip;
			OutputMessage( "on %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->node_port );
		}

		return SendShake( id, ip, port );
	} else {
		OutputMessage( "Client already exists...\n" );
		/* TODO */
		/* has the client replied yet? Y: then ignore this, N: now it has */
	}

	return true;
}

bool External::IsLocalNode( unsigned short port ) const
{
	for( ClientPtrList::const_iterator i = m_clients.begin( ), e = m_clients.end( ); i != e; i++ )
	{
		if( i->second->node_port == port )
		{
			return true;
		}
	}

	return false;
}

bool External::OnBroadcast( unsigned char* packet, unsigned int len, unsigned long ip, unsigned short port )
{
	unsigned short bport = 0;
	ip_address ipaddr;

	boost::shared_ptr<client_t> client = GetClient( ip, port );

	if( !client )
	{
		return false;
	}

	if( len < sizeof( bport ) )
	{
		OutputMessage( "Invalid len for BROADCAST: %u\n", len );

		return false;
	}

	memcpy( &bport, packet, sizeof( bport ) );
	packet += sizeof( bport );
	len -= sizeof( bport );

	g_App->GetListener( )->SendBroadcast( client, ( const char* )packet, len, bport );
	#if DEBUG
	OutputMessage( "Received %u bytes.\n", len );
	#endif

	memcpy( &ipaddr, &client->ip, sizeof( client->ip ) );

	#if DEBUG
	OutputMessage( "From %u.%u.%u.%u:%u\t",
		ipaddr.byte1,
		ipaddr.byte2,
		ipaddr.byte3,
		ipaddr.byte4,
		client->port );

	memcpy( &ipaddr, &client->node_ip, sizeof( client->node_ip ) );

	OutputMessage( "To %u.%u.%u.%u:%u\n",
		ipaddr.byte1,
		ipaddr.byte2,
		ipaddr.byte3,
		ipaddr.byte4,
		client->node_port );
	#endif

	client->pingTimeout.Reset( );
	client->pingInterval.Reset( );

	return true;
}

bool External::OnPing( unsigned char* packet, unsigned int len, unsigned long ip, unsigned short port )
{
	unsigned int id = 0;

	boost::shared_ptr<client_t> client = GetClient( ip, port );

	if( !client )
	{
		return false;
	}

	if( len < sizeof( id ) )
	{
		OutputMessage( "Invalid length for PING: %u\n", len );

		return false;
	}

	memcpy( &id, packet, sizeof( id ) );
	packet += sizeof( id );
	len -= sizeof( id );

	if( client->lastPingID != id )
	{
		if( !SendPing( client, id ) )
		{
			return false;
		}
	}

	client->pingTimeout.Reset( );
	client->pingInterval.Reset( );

	return true;
}

bool External::OnData( unsigned char* packet, unsigned int len, unsigned long ip, unsigned short port )
{
	unsigned short sport = 0;

	boost::shared_ptr<client_t> client = GetClient( ip, port );

	if( !client )
	{
		return false;
	}

	if( len < sizeof( sport ) )
	{
		OutputMessage( "Invalid length for DATA: %u\n", len );

		return false;
	}

	memcpy( &sport, packet, sizeof( sport ) );
	packet += sizeof( sport );
	len -= sizeof( sport );

	client->pingTimeout.Reset( );
	client->pingInterval.Reset( );

	return g_App->GetListener( )->SendPacket( client, ( const char* )packet, len, sport );
}

bool External::OnQuit( unsigned char* packet, unsigned int len, unsigned long ip, unsigned short port )
{
	ip_address ipaddr = *( ip_address* ) &ip;

	OutputMessage( "Terminating with %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

	DelClient( ip, port );

	return true;
}

bool External::Parse( boost::shared_ptr<unsigned char> packet, unsigned int len, unsigned long ip, unsigned short port )
{
	int retval = 0;
	unsigned char* data = packet.get( ) + 1;
	ip_address ipaddr = *( ip_address* ) &ip;

	if( len == 0 )
	{
		return false;
	}

	len--;

	switch( *packet )
	{
		default:
			{
				return false;
			}

		case E_PACKET_PING:
			{
				OutputMessage( "E_PACKET_PING from %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

				return OnPing( data, len, ip, port );
			}

		case E_PACKET_DATA:
			{
				OutputMessage( "E_PACKET_DATA from %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

				return OnData( data, len, ip, port );
			}

		case E_PACKET_BROADCAST:
			{
				OutputMessage( "E_PACKET_BROADCAST from %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

				return OnBroadcast( data, len, ip, port );
			}

		case E_PACKET_QUIT:
			{
				OutputMessage( "E_PACKET_QUIT from %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

				return OnQuit( data, len, ip, port );
			}

		case E_PACKET_SHAKE:
			{
				OutputMessage( "E_PACKET_SHAKE from %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, port );

				return OnShake( data, len, ip, port );
			}
	}
}

bool External::Read( void )
{
	unsigned long ip = 0;
	unsigned short port = 0;
	unsigned int len = 0;

	boost::optional<bool> result = m_sck->RecvFrom( 0, len, 0, 0 );

	if( !result )
	{
		OutputMessage( "External: Error obtaining length of data to receive.\n" );

		return false;
	}

	if( len == 0 )
	{
		// No data, no error
		return true;
	}

	boost::shared_ptr<unsigned char> buffer = boost::shared_ptr<unsigned char>( new unsigned char[len] );

	result = m_sck->RecvFrom( buffer.get( ), len, &ip, &port );

	if( !result || !*result )
	{
		OutputMessage( "External: Failed to receive.\n" );

		return false;
	}

	return Parse( buffer, len, ip, port );
}

bool External::Step( void )
{
	unsigned int len = 0;

	for( ClientPtrList::iterator i = m_clients.begin( ), e = m_clients.end( ); i != e; )
	{
		boost::shared_ptr<client_t> client = i->second;

		client->pingTimeout.Update( );

		if( client->pingTimeout.Fire( ) )
		{
			ip_address ipaddr = *( ip_address* ) &client->ip;

			OutputMessage( "Ping timeout for %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->port );

			i = m_clients.erase( i );
			continue;
		}

		client->pingInterval.Update( );

		if( client->pingInterval.Fire( ) )
		{
			ip_address ipaddr = *( ip_address* ) &client->ip;

			OutputMessage( "Sending ping to %u.%u.%u.%u:%u\n", ipaddr.byte1, ipaddr.byte2, ipaddr.byte3, ipaddr.byte4, client->port );
			client->lastPingID = client->pingInterval.Current( );
			SendPing( client, client->pingInterval.Current( ) );
			client->pingInterval.Reset( );
		}

		g_App->GetListener( )->Read( client );
		i++;
	}

	return Read( );
}

bool External::Incoming( const unsigned char* packet, unsigned int plen )
{
	const unsigned char* data = NULL;
	unsigned int len = 0;
	const ip_header* ih = NULL;
	const udp_header* uh = NULL;
	unsigned int ip_len = 0;
	unsigned short port = 0;

	ih = ( const ip_header* )( packet + 14 );
	ip_len = ( ih->ver_ihl & 0xF ) * 4;
	uh = ( const udp_header* )( ( const unsigned char* ) ih + ip_len );
	port = htons( uh->dport );
	len = plen - ( unsigned int ) ( ( ( unsigned char* ) uh + sizeof( *uh ) ) - packet );
	data = ( const unsigned char* ) uh + sizeof( *uh );

	#if DEBUG
	OutputMessage( "Broadcasting %u (%u - %u) bytes\n", len, plen, ( unsigned int ) ( ( ( unsigned char* ) uh + sizeof( *uh ) ) - packet ) );
	#endif

	for( ClientPtrList::iterator i = m_clients.begin( ), e = m_clients.end( ); i != e; i++ )
	{
		/*if( !client->registered ) {
			continue;
		}*/

		if( !SendBroadcast( i->second, data, len, port ) )
		{
			/* TODO: handle this error in some way... maybe... */
		}
	}

	return true;
}

} // namespace
